﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using FreeImageAPI;
using RayDen.Library.Core;
using RayDen.Library.Core.Noise;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Data.Imaging;
using RayDen.Library.Entity.Scene;
using RayDen.SceneLab.UI;

namespace RayDen.SceneLab.Controllers.FormControllers
{
    public class ProceduralTextureEditorController : EditorControllerBase<ProceduralTextureEditorForm, Bitmap>
    {
        public static string ParamsLegend = @"
                e - Epsilon
                p - v Epsilon
                nc - Single noise from coordinate
                nr - Single random noise
                nz - 3D noise from coordinate
                nzr - 3D random noise
                bpv - image pixel
                $n<float>,$ns<float,float,float>,$n3D
                $t<float,int>
                $saw,$spl
        ";
        private Bitmap bmp, baseBitmap;

        private RgbSpectrum[] textureOutput;

        public string ImageName { get; set; }
        public string BaseImageName { get; set; }

        public CancellationTokenSource CancellationTokenSource;
        private RgbSpectrumTexture baseTexture;
        public RgbSpectrum BaseColor;

        public string GenerationStatus { get; protected set; }

        public Bitmap BaseBitmap
        {
            get { return baseBitmap; }
            set
            {
                baseBitmap = value;
            }
        }

        public string[] Names
        {
            get
            {
                return this.vault.Select(item => item.Name).ToArray();
            }
        }


        public ProceduralTextureEditorController(Form owner)
            : base(owner)
        {
            BaseColor = new RgbSpectrum(1f);
            CancellationTokenSource = new CancellationTokenSource();
            if (ApplicationContext.Instance.ProceduralTextureEditor == null)
            {
                ApplicationContext.Instance.ProceduralTextureEditor = this;
            }
        }

        public void Clean()
        {
            if (this.form.Canvas.Image == null)
            {
                this.form.Canvas.Image = new Bitmap(this.form.Canvas.ClientRectangle.Width, this.form.Canvas.ClientRectangle.Height);
            }

            using (var gs = Graphics.FromImage(this.form.Canvas.Image))
            {
                gs.FillRegion(Brushes.Black, new Region(this.form.Canvas.ClientRectangle));
            }
        }

        public void SplatProvider(IRandomProvider rnd)
        {
            Clean();
            var canvas = this.form.Canvas;
            //using (var gs = Graphics.FromImage(this.canvas.Image))
            for (int i = 0; i < canvas.Image.Width * canvas.Image.Height; i++)
            {
                int x = Math.Min((int)(Math.Round(canvas.Image.Width * rnd.NextFloat())), canvas.Image.Width - 1);
                int y = Math.Min((int)(Math.Round(canvas.Image.Height * rnd.NextFloat())), canvas.Image.Height - 1);
                var col = (canvas.Image as Bitmap).GetPixel(x, y);
                if (col == Color.Black)
                {
                    (canvas.Image as Bitmap).SetPixel(x, y,
                        //Color.FromArgb((int)(255 * rnd.NextFloat()), (int)(255 * rnd.NextFloat()), (int)(255 * rnd.NextFloat()))
                        Color.Red
                        );
                }
                else
                {

                    col = col.G < 255 ? Color.FromArgb(0, col.R, col.G + 1, col.B) : col.B < 255 ? Color.FromArgb(0, col.R, col.G, col.B + 1) : Color.White;
                    x = Math.Max(0, Math.Min((int)(Math.Round(x + 2f * rnd.NextFloat() - 1f)), canvas.Image.Width - 1));
                    y = Math.Max(0, Math.Min((int)(Math.Round(y + 2f * rnd.NextFloat() - 1f)), canvas.Image.Height - 1));
                    (canvas.Image as Bitmap).SetPixel(x, y, col
                        //Color.FromArgb((int)(255 * rnd.NextFloat()), (int)(255 * rnd.NextFloat()), (int)(255 * rnd.NextFloat()))

                        );
                }
            }
            canvas.Refresh();
        }
        public void LoadBaseImage(string fileName)
        {
            BaseBitmap = new Bitmap(fileName);
            BaseImageName = fileName;
            baseTexture = ImageFactory.FreeImageLoadBitmap(fileName);
        }

        public void OpenFile(string fileName)
        {
            bmp = new FreeImageBitmap(fileName).ToBitmap();
            {
                this.State.Reset(bmp);
                this.ImageName = fileName;

            }
        }


        public void SaveFile(string fileName)
        {

        }



        public void Dispose()
        {
            if (bmp != null)
            {
                bmp.Dispose();
            }
            if (baseBitmap != null)
            {
                baseBitmap.Dispose();
            }
            if (CancellationTokenSource != null)
            {
                CancellationTokenSource.Dispose();
            }
        }

        int ImageWidth
        {
            get
            {
                return form.Canvas.ClientRectangle.Width;
            }
        }

        private int ImageHeight
        {
            get
            {
                return form.Canvas.ClientRectangle.Height;
            }
        }

        public Image Image { get { return bmp; } }
        public bool IsEvaluating { get; protected set; }

        private CancellationToken token;

        public async Task<int> Eval(string p)
        {
            IsEvaluating = true;

            this.form.Progress.Value = 0;
            this.form.Progress.Maximum = ImageHeight;
            token = CancellationTokenSource.Token;
            return await Task.Factory.StartNew(() =>
                {
                    try
                    {

                        if (bmp == null)
                        {
                            bmp = new Bitmap(ImageWidth, ImageHeight);
                            textureOutput = new RgbSpectrum[ImageWidth * ImageHeight];
                        }

                        //Parallel.For(0, ImageHeight,new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = 2 }, i => {


                        for (int i = 0; i < ImageHeight; i++)
                        {
                            for (int j = 0; j < ImageWidth; j++)
                            {
                                var u = j / (float)ImageWidth;
                                var v = i / (float)ImageHeight;
                                textureOutput[j + i * ImageWidth] = EvalTexture(p, u, v);
                            }
                            this.form.Invoke(new Action(() => form.Progress.Increment(1)));

                            if (i % 10 == 0)
                            {
                                this.form.Invoke(new Action(() =>
                                    {
                                        RefreshImage();
                                        form.Refresh();
                                    }));
                            }
                            GenerationStatus = i.ToString();
                        }
                        //    });


                        IsEvaluating = false;
                        texFunc = null;
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, "Error", MessageBoxButtons.OK,
                                        MessageBoxIcon.Error);
                        form.Close();
                    }
                    return 0;
                }, token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
        }

        public void CancelEvaluation()
        {
            IsEvaluating = false;
            texFunc = null;
            CancellationTokenSource.Cancel(true);
        }

        public void RefreshImage()
        {
            for (int i = 0; i < Image.Height; i++)
            {
                for (int j = 0; j < Image.Width; j++)
                {
                    bmp.SetPixel(j, i, textureOutput[j + i * Image.Width].ToColor());
                }

            }
        }

        private Func<object, RgbSpectrum> texFunc;
        private TextureVault vault = new TextureVault();
        private FastRandom rnd = new FastRandom();

        private RgbSpectrum EvalTexture(string expression, float x, float y)
        {
            if (texFunc == null)
            {
                var evaluator = new GenericExpressionEvaluator<RgbSpectrum>((i) =>
                    {
                        if (i is float)
                        {
                            return new RgbSpectrum((float)i);
                        }
                        if (i is double)
                        {
                            return new RgbSpectrum((float)((double)i));
                        }
                        return (RgbSpectrum)i;
                    },
                    RgbSpectrum.FromStringData);

                texFunc = evaluator.Compile(expression);

            }
            var p = new Vector(x, y, 2 - x - y);
            var p0 = new Vector(x, y, 0);

            var randomPoint = new Vector(rnd.NextFloat());

            var c = NoiseProvider.Instance.Noise(p);
            var ann = new
                {
                    unit = new RgbSpectrum(1f),
                    nc = c,
                    x,
                    y,
                    bpv = this.GetBasePixel(x, y),
                    c = new RgbSpectrum(x, y, 2 - x - y),
                    nz = new RgbSpectrum(NoiseProvider.Instance.Noise3D(p)),
                    nr = new RgbSpectrum(NoiseProvider.Instance.Noise(new Vector(rnd.NextFloat()))),
                    lw = new RgbSpectrum(0.5f, 0.2f, 0.3f),
                    dw = new RgbSpectrum(0.3f, 0.1f, 0.1f),
                    nzr =
                        new RgbSpectrum(
                            NoiseProvider.Instance.Noise3D(new Vector(rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat()))),
                    p = new RgbSpectrum(rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat()),
                    e = rnd.NextFloat(),
                };

            var eval = texFunc(ann);
            return BaseColor * eval;
        }

        private RgbSpectrum GetBasePixel(float x, float y)
        {
            return (RgbSpectrum)baseTexture.Sample(x, y);
        }

        internal void CleanImage()
        {
            textureOutput = new RgbSpectrum[ImageWidth * ImageHeight];
        }


        internal void SaveTexture(string name, string expression, RgbSpectrum baseColor, string baseTexturePath)
        {
            vault.Add(name, new SolidTextureInfo()
                {
                    BaseTexture = string.IsNullOrWhiteSpace(baseTexturePath) ? (TextureInfo)new ConstTextureInfo() { Color = (RgbSpectrumInfo)baseColor, Name = name } : new ImageTextureInfo() { FilePath = baseTexturePath, Name = baseTexturePath },
                    Expression = expression,
                    Name = name
                });
        }

        internal SolidTextureInfo LoadTexture(string name)
        {
            return (SolidTextureInfo)vault[name];
        }

        internal void SaveVault(string p)
        {
            vault.SaveXml(p, new[] { typeof(SolidTextureInfo), typeof(RgbSpectrum), typeof(Vector) });
        }

        internal void LoadVault(string p)
        {
            vault.LoadXml(p, new[] { typeof(SolidTextureInfo), typeof(RgbSpectrum), typeof(Vector) });
        }
    }
}
